package com.study.opcua.utils;

import com.alibaba.fastjson.JSONObject;
import org.eclipse.milo.opcua.sdk.client.OpcUaClient;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaSubscription;
import org.eclipse.milo.opcua.stack.core.AttributeId;
import org.eclipse.milo.opcua.stack.core.UaException;
import org.eclipse.milo.opcua.stack.core.types.builtin.*;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.enumerated.MonitoringMode;
import org.eclipse.milo.opcua.stack.core.types.enumerated.TimestampsToReturn;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoredItemCreateRequest;
import org.eclipse.milo.opcua.stack.core.types.structured.MonitoringParameters;
import org.eclipse.milo.opcua.stack.core.types.structured.ReadValueId;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;

/**
 * opcUA操作工具类
 */
public class OpcClientUtil {

    // 存放opc客户端的map
    private static ConcurrentHashMap<String, OpcUaClient> clients = new ConcurrentHashMap<>();

    /**
     * 添加客户端信息
     * @param key
     * @param client
     */
    public static void addClient(String key, OpcUaClient client){
        clients.put(key, client);
    }

    /**
     * 查询客户端
     * @param key
     * @return
     */
    public static OpcUaClient getClient(String key){
        return clients.get(key);
    }

    /**
     * 判断客户端是否存在
     * @param key
     * @return
     */
    public static boolean getContainsKey(String key){
        return clients.containsKey(key);
    }

    /**
     * 建立连接，并存储连接信息
     * @param url
     */
    public static void connectClient(String url){
        try {
            OpcUaClient opcUaClient = OpcUaClient.create(url);
            opcUaClient.connect();
            addClient(url, opcUaClient);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 读取单个节点数据
     * @param opcUaClient
     * @param nodeId
     * @return
     */
    public static Object readValue(OpcUaClient opcUaClient, NodeId nodeId){
        DataValue value;
        try {
            value = opcUaClient.readValue(0.0, TimestampsToReturn.Both, nodeId).get();
            return value.getValue().getValue();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 读取多个节点数据
     * @param opcUaClient
     * @param nodeIds
     * @return
     */
    public static Map<NodeId, Object> readValues(OpcUaClient opcUaClient, List<NodeId> nodeIds){
        Map<NodeId, Object> map = new HashMap<NodeId, Object>();
        CompletableFuture<List<DataValue>> providersResponse = opcUaClient.readValues(0.0, TimestampsToReturn.Both, nodeIds);
        List<DataValue> list =  providersResponse.join();
        for(int i=0;i< list.size();i++) {
            DataValue dataValue = list.get(i);
            map.put(nodeIds.get(i), dataValue.getValue().getValue());
        }
        return map;
    }

    /**
     * 写入数据
     * @param client
     * @param map
     * @param nameSpaceIndex
     * @return
     */
    public static boolean writeValue(OpcUaClient client,Map<String, Object> map,int nameSpaceIndex){
        if(map==null || map.size()==0)
            return true;

        for(Map.Entry<String, Object> entry:map.entrySet()){
            String key = entry.getKey();
            Object value = entry.getValue();
            Variant v = new Variant(value);
            DataValue dataValue = new DataValue(v,null,null);
            NodeId nodeId = new NodeId(nameSpaceIndex, key);
            StatusCode statusCode;
            try {
                statusCode = client.writeValue(nodeId,dataValue).get();
                boolean result = statusCode.isGood();
//				if(!result) {
//					return result;
//				}
            }catch (Exception e) {
                e.printStackTrace();
                return false;
            }

        }
        return true;
    }

    /**
     * 订阅数据模式
     * @param client
     * @param nodeIds
     * @param sf
     */
    public static void createSubscription(OpcUaClient client,List<NodeId> nodeIds, double sf){
        try {

            //创建发布间隔1000ms的订阅对象
            UaSubscription subscription = client.getSubscriptionManager().createSubscription(1000.0).get();

            List<MonitoredItemCreateRequest> requests = new ArrayList<>();

            for (NodeId nodeId : nodeIds) {

                ReadValueId readValueId = new ReadValueId(nodeId, AttributeId.Value.uid(), null, QualifiedName.NULL_VALUE);

                UInteger clientHandle = subscription.nextClientHandle();
                //	创建监控的参数
                MonitoringParameters parameters = new MonitoringParameters(clientHandle, sf, null, UInteger.valueOf(10), true);
                //	创建监控项请求
                //	该请求最后用于创建订阅。
                MonitoredItemCreateRequest request = new MonitoredItemCreateRequest(readValueId, MonitoringMode.Reporting, parameters);
                requests.add(request);
            }

            //	创建监控项，并且注册变量值改变时候的回调函数。
            List<UaMonitoredItem> items = subscription.createMonitoredItems(
                    TimestampsToReturn.Both,
                    requests,
                    (item,id)->{
                        item.setValueConsumer((item1, value)->{
                            // 订阅当前节点和值
//                            logger.info("subscription value received: item={}, value={}",item1.getReadValueId().getNodeId(), value.getValue());
                        });
                    }
            ).get();

        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

    }

    public static void main(String[] args) throws Exception {
        OpcUaClient opcUaClient = OpcUaClient.create("opc.tcp://127.0.0.1:49320");
        opcUaClient.connect();
        NodeId nodeId = new NodeId(2, "通道 1.设备 1.x");
        DataValue dataValue = opcUaClient.readValue(0.0, TimestampsToReturn.Both, nodeId).get();

        System.out.println("---------------获取到数据：" + JSONObject.toJSONString(dataValue));
        Variant value = dataValue.getValue();
        System.out.println("---------------获取到数据：" + JSONObject.toJSONString(value));
        Object value1 = value.getValue();
        System.out.println("---------------获取到数据：" + JSONObject.toJSONString(value1));
    }
}
